
#include "Common.hpp"
using namespace std;

namespace {

//-----------------------------------------------------------------------------
const char* nVertexProgramSrc =
    "!!NVvp4.0"
    NL
    NL "main:"
    NL "    MOV result.position, vertex.attrib[0];"
    NL "    RET;"
    NL "END";

const char* nFragmentProgramSrc[] = {
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL
    NL "FLOAT OUTPUT oColor = result.color;"
    NL
    NL "TEMP R0, R1, R2, R3, R4;"
    NL "FLOAT TEMP sinVal;"
    NL
    NL "# in: R0 - fragment position in the range [-1.0, 1.0]"
    NL "# out: R1 - color"
    NL "Deform:"
    NL "    # R1.xy = vec2(sin(1.1 * Time + R0.x), sin(1.2 * Time + R0.y));"
    NL "    MAD R1.x, Time, 1.1, R0;"
    NL "    SIN R1.x, R1.x;"
    NL "    MAD R1.y, Time, 1.2, R0;"
    NL "    SIN R1.y, R1.y;"
    NL "    # R1.z = length(R2.xy);"
    NL "    DP2 R1.z, R1, R1;"
    NL "    RSQ R1.z, R1.z;"
    NL "    RCP R1.z, R1.z;"
    NL "    # R1.w = sqrt(R1.z * R1.z + 1.0);"
    NL "    MAD R1.w, R1.zzzz, R1.zzzz, 1.0;"
    NL "    RSQ R1.w, R1.w;"
    NL "    RCP R1.w, R1.w;"
    NL "    # R1.x = sin(0.0 + 1.0 * Time) + R0.x * R1.w;"
    NL "    MAD R1.x, R0.xxxx, R1.wwww, sinVal.xxxx;"
    NL "    # R1.y = sin(0.6 + 1.1 * Time) + R0.y * R1.w;"
    NL "    MAD R1.y, R0.yyyy, R1.wwww, sinVal.yyyy;"
    NL "    # R1.xy = 0.5 * R1.xy;"
    NL "    MUL R1.xy, R1, 0.5;"
    NL "    TEX R1, R1, texture[0], 2D;"
    NL "    RET;"
    NL
    NL "main:"
    NL "    # precompute sin values used in Deform function"
    NL "    MAD sinVal.x, Time, 1.0, 0.0;"
    NL "    SIN sinVal.x, sinVal.x;"
    NL "    MAD sinVal.y, Time, 1.1, 0.6;"
    NL "    SIN sinVal.y, sinVal.y;"
    NL "    # convert fragment position to the range [-1.0, 1.0]"
    NL "    FLOAT TEMP p;"
    NL "    RCP p.x, Resolution.x;"
    NL "    RCP p.y, Resolution.y;"
    NL "    MUL p.xy, fragment.position, p;"
    NL "    MAD p.xy, p, 2.0, -1.0;"
    NL "    # loop init"
    NL "    MOV R0.xy, p;"
    NL "    MOV R2.xyz, 0.0;"
    NL "    DIV R3.xy, -p, 40.0;"
    NL "    MOV R3.z, 1.0;"
    NL "    REP 40;"
    NL "        CAL Deform;"
    NL "        # R1.xyz = smoothstep(0.1, 1.0, R1.xyz * R1.xyz);"
    NL "        MUL R1.xyz, R1, R1;"
    NL "        SUB R1.xyz, R1, 0.1;"
    NL "        MUL.SAT R1.xyz, R1, 1.111111111;"
    NL "        MUL R4.xyz, R1, R1;"
    NL "        MAD R1.xyz, R1, -2.0, 3.0;"
    NL "        MUL R1.xyz, R1, R4;"
    NL "        # R2.xyz += R1 * R3.z;"
    NL "        MAD R2.xyz, R1, R3.zzzz, R2;"
    NL "        # R3.z *= 0.99;"
    NL "        MUL R3.z, R3.zzzz, 0.99;"
    NL "        # R0.xy += R3;"
    NL "        ADD R0.xy, R0, R3;"
    NL "    ENDREP;"
    NL "    # R2.xyz = R2 / 40.0;"
    NL "    DIV R2.xyz, R2, 40.0;"
    NL "    # R3.x = 1.5 / (dot(p, p) + 1.0);"
    NL "    DP2 R3.x, p, p;"
    NL "    ADD R3.x, R3, 1.0;"
    NL "    RCP R3.x, R3.x;"
    NL "    MUL R3.x, 1.5, R3;"
    NL "    # oColor.rgb = R2 * R3.x;"
    NL "    MUL oColor.rgb, R2, R3.xxxx;"
    NL "    MOV oColor.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1;"
    NL
    NL "main:"
    NL "    FLOAT TEMP p;"
    NL "    MAD p.xy, fragment.position, 2.0, -Resolution;"
    NL "    DIV p.xy, p, Resolution.y;"
    NL
    NL "    ABS p.zw, p.yxyx;                                 # p.x = x; p.y = y; p.z = abs(y); p.w = abs(x);"
    NL "    MAX R0.x, p.zzzz, p.wwww;                         # R0.x = max(abs(x), abs(y));"
    NL "    MIN R0.y, p.zzzz, p.wwww;                         # R0.y = min(abs(x), abs(y));"
    NL "    RCP R0.z, R0.x;                                   # R0.z = 1 / R0.x;"
    NL "    MUL R0.z, R0.yyyy, R0.zzzz;                       # R0.z = R0.y / R0.x;"
    NL "    MUL R0.w, R0.zzzz, R0.zzzz;                       # R0.w = R0.z * R0.z;"
    NL "    MOV R0.x, -0.013480470;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.057477314;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.121239071;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.195635925;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.332994597;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.999995630;"
    NL "    MUL R0.x, R0.xxxx, R0.zzzz;"
    NL "    SGT.CC0 R1.x, p.wwww, p.zzzz;                     # if (abs(y) > abs(x)) R0.x = 1.57.. - R0.x;"
    NL "    SUB R0.x (NE0.xxxx), 1.570796327, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.yyyy, 0;                          # if (x < 0) R0.x = 3.14.. - R0.x;"
    NL "    SUB R0.x (NE0.xxxx), 3.141592654, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.xxxx, 0;                          # if (y < 0) R0.x = -R0.x;"
    NL "    MOV R0.x (NE0.xxxx), -R0.xxxx;                    # R0.x = atan(p.x, p.y);"
    NL
    NL "    DP2 R0.y, p, p;"
    NL "    RSQ R0.y, R0.y;"
    NL "    RCP R0.y, R0.y;"
    NL "    MUL R0.y, R0.yyyy, 0.75;                          # R0.y = length(p) * 0.75;"
    NL
    NL "    MUL R0.z, Time, 3.141592654;"
    NL "    MAD R0.z, -R0.yyyy, 2.0, R0.zzzz;"
    NL "    COS R0.z, R0.z;                                   # R0.z = cos(3.14.. * Time - R0.y * 2);"
    NL
    NL "    MUL R0.w, R0.xxxx, 12.0;"
    NL "    MAD R0.w, -R0.zzzz, 7.0, R0.wwww;"
    NL "    MAD R1.w, R0.yyyy, 8.0, R0.wwww;"
    NL "    COS R0.w, R1.w;"
    NL "    MAD R0.w, R0.wwww, 0.5, 0.5;                      # R0.w = 0.5 + 0.5 * cos(12.0 * R0.x - R0.z * 7.0 + R0.y * 8.0);"
    NL
    NL "    POW R1.y, R0.w, R0.y;"
    NL "    MAD R1.x, R0.zzzz, 0.3, 0.7;"
    NL "    MUL R1.x, R1.xxxx, R1.yyyy;"
    NL "    MAD R1.x, R1.xxxx, 0.75, 0.25;                    # R1.x = 0.25 + 0.75 * pow(R0.w, R0.y) * (0.7 + 0.3 * R0.z);"
    NL
    NL "    MOV R1.y, 1.0;"
    NL "    SUB.CC0 R1.z, R1.xxxx, R0.yyyy;"
    NL "    MOV R1.y (LE0.zzzz), 0;"
    NL "    DIV R1.z, R0.yyyy, R1.x;"
    NL "    SUB R1.z, 1.0, R1.zzzz;"
    NL "    RSQ R1.z, R1.z;"
    NL "    RCP R1.z, R1.z;"
    NL "    MUL R1.y, R1.yyyy, R1.zzzz;"
    NL "    MUL R1.y, R1.yyyy, R0.yyyy;"
    NL "    MUL R1.y, R1.yyyy, 2.5;                           # R1.y = (R1.x - R0.y < 0 ? 0 : 1.0) * sqrt(1.0 - R0.y / R1.x) * R0.y * 2.5;"
    NL
    NL "    MUL R1.z, R1.wwww, 0.5;"
    NL "    COS R1.z, R1.z;"
    NL "    MAD R1.z, R1.zzzz, 0.25, 1.25;"
    NL "    MUL R1.y, R1.yyyy, R1.zzzz;"
    NL
    NL "    COS R1.w, R1.w;"
    NL "    MAD R1.w, R1.wwww, 0.5, 0.5;"
    NL "    MUL R1.w, R1.wwww, 0.35;"
    NL "    MUL R1.z, R0.yyyy, 30.0;"
    NL "    SIN R1.z, R1.z;"
    NL "    MAD R1.z, R1.zzzz, 0.5, 0.5;"
    NL "    MUL R1.z, R1.zzzz, R1.wwww;"
    NL "    SUB R1.z, 1.0, R1.zzzz;"
    NL "    MUL R1.y, R1.yyyy, R1.zzzz;"
    NL
    NL "    SUB R1.z, 1.0, R0.yyyy;"
    NL "    MUL R1.z, R1.zzzz, 0.35;"
    NL "    MUL R1.z, R1.zzzz, R0.wwww;"
    NL "    MAD R1.z, R0.yyyy, 0.2, R1.zzzz;"
    NL "    MAD R1.z, -R0.wwww, 0.5, R1.yyyy;"
    NL
    NL "    SUB R1.w, 1.0, R0.yyyy;"
    NL "    MUL R1.w, R1.wwww, R0.wwww;"
    NL "    MUL R1.w, R1.wwww, 0.1;"
    NL "    MAD R1.w, -R0.wwww, R0.yyyy, R1.yyyy;"
    NL
    NL "    MOV result.color.rgb, R1.yzww;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1;"
    NL
    NL "main:"
    NL "    FLOAT TEMP p;                             # convert fragment position to the [-1.0; 1.0] range"
    NL "    RCP p.x, Resolution.x;"
    NL "    RCP p.y, Resolution.y;"
    NL "    MUL p.xy, fragment.position, p;"
    NL "    MAD p.xy, p, 2.0, -1.0;"
    NL
    NL "    ABS p.zw, p.yxyx;                         # R0.x = atan(p.y, p.x);"
    NL "    MAX R0.x, p.zzzz, p.wwww;"
    NL "    MIN R0.y, p.zzzz, p.wwww;"
    NL "    RCP R0.z, R0.x;"
    NL "    MUL R0.z, R0.yyyy, R0.zzzz;"
    NL "    MUL R0.w, R0.zzzz, R0.zzzz;"
    NL "    MOV R0.x, -0.013480470;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.057477314;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.121239071;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.195635925;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.332994597;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.999995630;"
    NL "    MUL R0.x, R0.xxxx, R0.zzzz;"
    NL "    SGT.CC0 R1.x, p.wwww, p.zzzz;"
    NL "    SUB R0.x (NE0.xxxx), 1.570796327, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.yyyy, 0;"
    NL "    SUB R0.x (NE0.xxxx), 3.141592654, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.xxxx, 0;"
    NL "    MOV R0.x (NE0.xxxx), -R0.xxxx;"
    NL
    NL "    DP2 R0.y, p, p;     # R0.y = sqrt(dot(p, p));"
    NL "    RSQ R0.y, R0.y;"
    NL "    RCP R0.y, R0.y;"
    NL
    NL "    DIV R0.z, 0.1, R0.y;           # R0.z = 0.75 * Time + 0.1 / R0.y;"
    NL "    MAD R0.z, Time, 0.75, R0.z;"
    NL "    DIV R0.w, R0.x, 3.141592654;   # R0.w = R0.x / 3.14..;"
    NL
    NL "    TEX R1, R0.zwzw, texture[0], 2D;"
    NL "    MUL R1, R1, R0.y;"
    NL "    MOV result.color.rgb, R1;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1, R2, R3;"
    NL
    NL "main:"
    NL "    FLOAT TEMP p;                             # convert fragment position to the [-1.0, 1.0] range"
    NL "    RCP p.x, Resolution.x;"
    NL "    RCP p.y, Resolution.y;"
    NL "    MUL p.xy, fragment.position, p;"
    NL "    MAD p.xy, p, 2.0, -1.0;"
    NL
    NL "    ABS p.zw, p.yxyx;                         # R0.x = atan(p.y, p.x);"
    NL "    MAX R0.x, p.zzzz, p.wwww;"
    NL "    MIN R0.y, p.zzzz, p.wwww;"
    NL "    RCP R0.z, R0.x;"
    NL "    MUL R0.z, R0.yyyy, R0.zzzz;"
    NL "    MUL R0.w, R0.zzzz, R0.zzzz;"
    NL "    MOV R0.x, -0.013480470;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.057477314;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.121239071;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.195635925;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.332994597;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.999995630;"
    NL "    MUL R0.x, R0.xxxx, R0.zzzz;"
    NL "    SGT.CC0 R1.x, p.wwww, p.zzzz;"
    NL "    SUB R0.x (NE0.xxxx), 1.570796327, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.yyyy, 0;"
    NL "    SUB R0.x (NE0.xxxx), 3.141592654, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.xxxx, 0;"
    NL "    MOV R0.x (NE0.xxxx), -R0.xxxx;"
    NL
    NL "    DP2 R0.y, p, p;              # R0.y = sqrt(dot(p, p));"
    NL "    RSQ R0.y, R0.y;"
    NL "    RCP R0.y, R0.y;"
    NL
    NL "    MUL R0.z, Time, 0.5;         # R0.x = atan(p.y, p.x) + 0.5 * sin(0.5 * R0.y - 0.5 * Time);"
    NL "    MAD R0.z, R0.y, 0.5, -R0.z;"
    NL "    SIN R0.z, R0.z;"
    NL "    MAD R0.x, R0.z, 0.5, R0.x;"
    NL
    NL "    MUL R0.z, R0.x, 7.0;         # R0.z = 0.5 + 0.5 * cos(7.0 * R0.x);"
    NL "    COS R0.z, R0.z;"
    NL "    MAD R0.z, R0.z, 0.5, 0.5;"
    NL "    MUL R1.x, R0.z, R0.z;        # R0.z = smoothstep(0.0, 1.0, R0.z);"
    NL "    MUL R0.w, R1.x, 3.0;"
    NL "    MUL R0.z, R1.x, R0.z;"
    NL "    MUL R0.z, R0.z, 2.0;"
    NL "    SUB R0.z, R0.w, R0.z;"
    NL "    MUL R1.x, R0.z, R0.z;        # R0.z = smoothstep(0.0, 1.0, R0.z);"
    NL "    MUL R0.w, R1.x, 3.0;"
    NL "    MUL R0.z, R1.x, R0.z;"
    NL "    MUL R0.z, R0.z, 2.0;"
    NL "    SUB R0.z, R0.w, R0.z;"
    NL "    MUL R1.x, R0.z, R0.z;        # R0.z = smoothstep(0.0, 1.0, R0.z);"
    NL "    MUL R0.w, R1.x, 3.0;"
    NL "    MUL R0.z, R1.x, R0.z;"
    NL "    MUL R0.z, R0.z, 2.0;"
    NL "    SUB R0.z, R0.w, R0.z;"
    NL "    MUL R1.x, R0.z, R0.z;        # R0.z = smoothstep(0.0, 1.0, R0.z);"
    NL "    MUL R0.w, R1.x, 3.0;"
    NL "    MUL R0.z, R1.x, R0.z;"
    NL "    MUL R0.z, R0.z, 2.0;"
    NL "    SUB R0.z, R0.w, R0.z;"
    NL
    NL "    MAD R1.x, R0.z, 0.2, R0.y;   # R1.x = Time + 1.0 / (R0.y + 0.2 * R0.z);"
    NL "    RCP R1.x, R1.x;"
    NL "    ADD R1.x, R1.x, Time;"
    NL "    DIV R1.y, R0.x, 3.141592654; # R1.y = 3.0 * R0.x / 3.14..;"
    NL "    MUL R1.y, R1.y, 3.0;"
    NL
    NL "    TEX R1, R1, texture[0], 2D;"
    NL
    NL "    MAD R2.x, R0.z, 0.5, 0.5;    # R2.x = (0.5 + 0.5 * R0.z) * R0.y * R0.y;"
    NL "    MUL R2.x, R2.x, R0.y;"
    NL "    MUL R2.x, R2.x, R0.y;"
    NL
    NL "    MUL R2.y, R0.x, 7.0;"
    NL "    COS R2.y, R2.y;"
    NL "    MAD R2.y, R2.y, 0.5, 0.5;"
    NL "    DIV.SAT R2.z, R2.y, 0.4;     # R2.z = smoothstep(0.0, 0.4, R2.y);"
    NL "    MUL R2.z, R2.z, R2.z;"
    NL "    MUL R2.w, R2.z, 3.0;"
    NL "    MUL R2.z, R2.z, R2.z;"
    NL "    MUL R2.z, R2.z, 2.0;"
    NL "    SUB R2.z, R2.w, R2.z;"
    NL "    SUB R3.z, R2.y, 0.4;         # R3.z = smoothstep(0.4, 0.7, R2.y);"
    NL "    DIV.SAT R3.z, R3.z, 0.3;"
    NL "    MUL R3.z, R3.z, R3.z;"
    NL "    MUL R3.w, R3.z, 3.0;"
    NL "    MUL R3.z, R3.z, R3.z;"
    NL "    MUL R3.z, R3.z, 2.0;"
    NL "    SUB R3.z, R3.w, R3.z;"
    NL "    SUB R2.y, R2.z, R3.z;        # R2.y = 1.0 - ((R2.z - R3.z) * 0.5 * R0.y);"
    NL "    MUL R2.y, R2.y, 0.5;"
    NL "    MUL R2.y, R2.y, R0.y;"
    NL "    SUB R2.y, 1.0, R2.y;"
    NL
    NL "    MUL R1, R1, R2.x;            # R1 = R1 * R2.x * R2.y;"
    NL "    MUL R1, R1, R2.y;"
    NL "    MOV result.color.rgb, R1;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1;"
    NL
    NL "main:"
    NL "    FLOAT TEMP p;                             # convert fragment position to the [-1.0, 1.0] range"
    NL "    RCP p.x, Resolution.x;"
    NL "    RCP p.y, Resolution.y;"
    NL "    MUL p.xy, fragment.position, p;"
    NL "    MAD p.xy, p, 2.0, -1.0;"
    NL
    NL "    ABS p.zw, p.yxyx;                         # R0.x = atan(p.y, p.x);"
    NL "    MAX R0.x, p.zzzz, p.wwww;"
    NL "    MIN R0.y, p.zzzz, p.wwww;"
    NL "    RCP R0.z, R0.x;"
    NL "    MUL R0.z, R0.yyyy, R0.zzzz;"
    NL "    MUL R0.w, R0.zzzz, R0.zzzz;"
    NL "    MOV R0.x, -0.013480470;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.057477314;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.121239071;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.195635925;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, -0.332994597;"
    NL "    MAD R0.x, R0.xxxx, R0.wwww, 0.999995630;"
    NL "    MUL R0.x, R0.xxxx, R0.zzzz;"
    NL "    SGT.CC0 R1.x, p.wwww, p.zzzz;"
    NL "    SUB R0.x (NE0.xxxx), 1.570796327, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.yyyy, 0;"
    NL "    SUB R0.x (NE0.xxxx), 3.141592654, R0.xxxx;"
    NL "    SLT.CC0 R1.x, p.xxxx, 0;"
    NL "    MOV R0.x (NE0.xxxx), -R0.xxxx;"
    NL
    NL "    MUL R0.y, p.x, p.x;          # R0.y = pow(pow(p.x * p.x, 16.0) + pow(p.y * p.y, 16.0), 1.0 / 32.0);"
    NL "    POW R0.y, R0.y, 16.0;"
    NL "    MUL R0.z, p.y, p.y;"
    NL "    POW R0.z, R0.z, 16.0;"
    NL "    ADD R0.y, R0.y, R0.z;"
    NL "    POW R0.y, R0.y, 0.03125;"
    NL
    NL "    RCP R0.z, R0.y;              # R0.z = 0.5 * Time + 0.5 / R0.y;"
    NL "    MUL R0.z, R0.z, 0.5;"
    NL "    MAD R0.z, Time, 0.5, R0.z;"
    NL "    DIV R0.w, R0.x, 3.141592654; # R0.w = R0.x / 3.14..;"
    NL "    TEX R1, R0.zwzw, texture[0], 2D;"
    NL
    NL "    MUL R1, R1, R0.y;            # R1 = R1 * R0.y * R0.y * R0.y;"
    NL "    MUL R1, R1, R0.y;"
    NL "    MUL R1, R1, R0.y;"
    NL "    MOV result.color.rgb, R1;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1;"
    NL
    NL "main:"
    NL "    FLOAT TEMP p;                     # convert fragment position to the [-1.0, 1.0] range"
    NL "    RCP p.x, Resolution.x;"
    NL "    RCP p.y, Resolution.y;"
    NL "    MUL p.xy, fragment.position, p;"
    NL "    MAD p.xy, p, 2.0, -1.0;"
    NL
    NL "    MUL R0.x, Time, 0.25;             # R0.x = p.x * cos(0.25 * Time) - p.y * sin(0.25 * Time);"
    NL "    COS R0.y, R0.x;                   # R0.y = p.x * sin(0.25 * Time) + p.y * cos(0.25 * Time);"
    NL "    SIN R0.x, R0.x;"
    NL "    MUL R1, p.xyxy, R0.xyyx;"
    NL "    ADD R0.x, R1.z, -R1.w;"
    NL "    ADD R0.y, R1.x, R1.y;"
    NL
    NL "    DIV R1.x, R0.x, |R0.y|;           # R1.x = 0.25 * R0.x / abs(R0.y);"
    NL "    MUL R1.x, R1.x, 0.25;"
    NL "    DIV R1.y, 0.25, |R0.y|;           # R1.y = 0.2 * Time + 0.25 / abs(R0.y);"
    NL "    MAD R1.y, Time, 0.2, R1.y;"
    NL
    NL "    TEX R1, R1, texture[0], 2D;"
    NL "    MUL R1, R1, R0.y;                 # R1 = R1 * R0.y * R0.y;"
    NL "    MUL R1, R1, R0.y;"
    NL "    MOV result.color.rgb, R1;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
    ,
    "!!NVfp4.0"
    NL "FLOAT PARAM Time = program.env[0];"
    NL "FLOAT PARAM Resolution = program.env[1];"
    NL "FLOAT TEMP R0, R1, R2;"
    NL
    NL "main:"
    NL "    MUL R0.xy, Resolution, 0.5;         # R0.xy = Resolution * 0.5;"
    NL "    MOV R0.zw, fragment.position.xyxy;  # R0.zw = gl_FragCoord.xy;"
    NL "    COS R1.x, Time.x;                   # R1.x = cos(Time);"
    NL "    MUL R1.y, Time, 0.5;                # R1.y = sin(0.5 * Time);"
    NL "    SIN R1.y, R1.y;"
    NL "    MUL R1.z, Time, 0.2;                # R1.z = sin(0.2 * Time);"
    NL "    SIN R1.z, R1.z;"
    NL "    MUL R2.x, 0.5, R0.x;                # R0.z -= 0.5 * R0.x * sin(0.5 * Time) + 0.3 * R0.x * cos(Time) + R0.x;"
    NL "    MAD R2.x, R2.x, R1.y, R0.x;"
    NL "    MUL R2.y, 0.3, R0.x;"
    NL "    MAD R2.x, R2.y, R1.x, R2.x;"
    NL "    SUB R0.z, R0.z, R2.x;"
    NL "    MUL R2.y, 0.4, R0.y;                # R0.w -= 0.4 * R0.y * sin(0.2 * Time) + 0.3 * R0.y * cos(Time) + R0.y;"
    NL "    MAD R2.y, R2.y, R1.z, R0.y;"
    NL "    MUL R2.z, 0.3, R0.y;"
    NL "    MAD R2.y, R2.z, R1.x, R2.y;"
    NL "    SUB R0.w, R0.w, R2.y;"
    NL "    DP2 R0.x, R0.zwzw, R0.zwzw;         # R0.x = length(R0.zw);"
    NL "    RSQ R0.x, R0.x;"
    NL "    RCP R0.x, R0.x;"
    NL "    RCP R1.x, Resolution.x;             # R1.xy = gl_FragCoord / Resolution.xy;"
    NL "    RCP R1.y, Resolution.y;"
    NL "    MUL R1.xy, fragment.position, R1;"
    NL "    DIV R1.z, R0.x, 30.0;               # R2.xy = R1.xy + R0.zw / R0.x * sin(R0.x / 30.0 - Time * 10.0) / 25.0;"
    NL "    MAD R1.z, -Time, 10.0, R1.z;"
    NL "    SIN R1.z, R1.z;"
    NL "    MUL R1.z, R1.z, 0.04;"
    NL "    DIV R2.xy, R0.zwzw, R0.x;"
    NL "    MAD R2.xy, R2.xyxy, R1.zzzz, R1.xyxy;"
    NL "    TEX R1, R2, texture[0], 2D;"
    NL "    MUL R1, R1, 50.0;                   # R1 = R1 * 50 / R0.x;"
    NL "    DIV R1, R1, R0.x;"
    NL "    MOV result.color.rgb, R1;"
    NL "    MOV result.color.a, 1.0;"
    NL "    RET;"
    NL "END"
};
//-----------------------------------------------------------------------------
Window* nWindow;
SDL_GLContext nContext;

const int EFFECT_COUNT = 7;
GLuint nEffectTexture[EFFECT_COUNT];
int nCurrentEffect = 0;

// OpenGL resources
GLuint nVertexProgram, nFragmentProgram[EFFECT_COUNT];
GLuint nVBO;
GLuint64 nVBOAddress;
GLuint nTexture[4];

ArcBall nArcBall;
//-----------------------------------------------------------------------------
void Setup() {
    // window
    SDL_DisplayMode dmode;
    SDL_GetDesktopDisplayMode(0, &dmode);
    nWindow = new Window("Program Effects", dmode.w, dmode.h,
            SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS);

    // OpenGL context
    nContext = SDL_GL_CreateContext(nWindow->Get());
    if (!nContext) {
        throw runtime_error("[Setup] SDL_GL_CreateContext failed.");
    }

    SDL_GL_SetSwapInterval(0);

    if (glewInit() != GLEW_OK) {
        throw runtime_error("[Setup] glewInit failed.");
    }
    if (!GLEW_NV_gpu_program4) {
        throw runtime_error("[Setup] GL_NV_gpu_program4 extension is not supported.");
    }

    // vertex program
    glGenProgramsARB(1, &nVertexProgram);
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nVertexProgram);
    LoadGLProgram(GL_VERTEX_PROGRAM_ARB, nVertexProgramSrc);
    cout << GetGLProgramInfo(GL_VERTEX_PROGRAM_ARB);

    // fragment programs
    glGenProgramsARB(EFFECT_COUNT, nFragmentProgram);
    for (int i = 0; i < EFFECT_COUNT; ++i) {
        glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgram[i]);
        LoadGLProgram(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgramSrc[i]);
        cout << GetGLProgramInfo(GL_FRAGMENT_PROGRAM_ARB);
    }

    // VBO
    const float vertices[] = { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f };

    glGenBuffers(1, &nVBO);
    glBindBuffer(GL_ARRAY_BUFFER, nVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glGetBufferParameterui64vNV(GL_ARRAY_BUFFER, GL_BUFFER_GPU_ADDRESS_NV, &nVBOAddress);
    glMakeBufferResidentNV(GL_ARRAY_BUFFER, GL_READ_ONLY);

    // load texture
    glGenTextures(SDL_arraysize(nTexture), nTexture);

    int w, h, n;
    stbi_uc* pixels = stbi_load("../../media/tex1.jpg", &w, &h, &n, 0);
    if (!pixels) {
        throw runtime_error("[Setup] Failed to load ../../media/tex1.jpg.");
    }

    glBindTexture(GL_TEXTURE_2D, nTexture[0]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, n == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    stbi_image_free(pixels);

    pixels = stbi_load("../../media/tex3.jpg", &w, &h, &n, 0);
    if (!pixels) {
        throw runtime_error("[Setup] Failed to load ../../media/tex3.jpg.");
    }

    glBindTexture(GL_TEXTURE_2D, nTexture[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, n == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    stbi_image_free(pixels);

    pixels = stbi_load("../../media/tex0.jpg", &w, &h, &n, 0);
    if (!pixels) {
        throw runtime_error("[Setup] Failed to load ../../media/tex0.jpg.");
    }

    glBindTexture(GL_TEXTURE_2D, nTexture[2]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, n == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    stbi_image_free(pixels);

    pixels = stbi_load("../../media/tex2.jpg", &w, &h, &n, 0);
    if (!pixels) {
        throw runtime_error("[Setup] Failed to load ../../media/tex2.jpg.");
    }

    glBindTexture(GL_TEXTURE_2D, nTexture[3]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, n == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    stbi_image_free(pixels);

    // GL state
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_VERTEX_PROGRAM_ARB);
    glEnable(GL_FRAGMENT_PROGRAM_ARB);

    // GUI
    TwInit(TW_OPENGL, NULL);
    TwWindowSize(nWindow->Width(), nWindow->Height());

    TwBar* bar = TwNewBar("Menu");
    TwDefine("Menu color='0 0 0' alpha=128 position='10 10' size='240 60'");
    TwDefine("Menu fontresizable=false resizable=false movable=false valueswidth=100");

    const TwEnumVal v[] = {
        { 0, "Radial Blur" },
        { 1, "Flower" },
        { 2, "Tunnel" },
        { 3, "Relief Tunnel" },
        { 4, "Square Tunnel" },
        { 5, "Fly" },
        { 6, "Pulse" },
    };

    const TwType type = TwDefineEnum("EffectsEnum", v, SDL_arraysize(v));
    TwAddVarRW(bar, "Effect", type, &nCurrentEffect, "");

    nArcBall.SetWindowSize(nWindow->Width(), nWindow->Height());

    nEffectTexture[0] = nTexture[0];
    nEffectTexture[1] = 0;
    nEffectTexture[2] = nTexture[0];
    nEffectTexture[3] = nTexture[1];
    nEffectTexture[4] = nTexture[2];
    nEffectTexture[5] = nTexture[3];
    nEffectTexture[6] = nTexture[0];
}
//-----------------------------------------------------------------------------
bool ProcessEvents() {
    SDL_Event evt;
    while (SDL_PollEvent(&evt)) {
        if (evt.type == SDL_QUIT) {
            return false;
        }

        if (ProcessCommonEvent(evt) || ProcessGUIEvent(evt)) {
            continue;
        }

        switch (evt.type) {
        case SDL_MOUSEBUTTONDOWN:
            if (evt.button.button == SDL_BUTTON_LEFT) {
                nArcBall.Begin(evt.button.x, evt.button.y);
                return true;
            }
            break;

        case SDL_MOUSEBUTTONUP:
            if (evt.button.button == SDL_BUTTON_LEFT) {
                nArcBall.End();
                return true;
            }
            break;

        case SDL_MOUSEMOTION:
            nArcBall.Drag(evt.motion.x, evt.motion.y);
            break;
        }
    }

    return true;
}
//-----------------------------------------------------------------------------
void Update() {
    const float currentTime = SDL_GetTicks() * 0.001f;

    // clear buffers
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, nEffectTexture[nCurrentEffect]);

    // draw
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, nVertexProgram);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, nFragmentProgram[nCurrentEffect]);

    glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 0, currentTime, currentTime, currentTime, currentTime);
    glProgramEnvParameter4fARB(GL_FRAGMENT_PROGRAM_ARB, 1, static_cast<float>(nWindow->Width()), static_cast<float>(nWindow->Height()), 0.0f, 0.0f);

    glEnableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glEnableVertexAttribArray(0);
    glVertexAttribFormatNV(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float));
    glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, 0, nVBOAddress, 4 * 2 * sizeof(float));
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // draw GUI
    glDisableClientState(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
    glDisableClientState(GL_ELEMENT_ARRAY_UNIFIED_NV);
    for (GLuint i = 0; i < 16; ++i) {
        glDisableVertexAttribArray(i);
    }
    TwDraw();

    nWindow->Swap();
}
//-----------------------------------------------------------------------------
void Run() {
    Setup();

    while (ProcessEvents()) {
        Update();
    }
}
//-----------------------------------------------------------------------------
void Cleanup() {
    glDeleteBuffers(1, &nVBO);
    glDeleteProgramsARB(1, &nVertexProgram);
    glDeleteProgramsARB(EFFECT_COUNT, nFragmentProgram);
    glDeleteTextures(SDL_arraysize(nTexture), nTexture);

    TwTerminate();

    SDL_GL_DeleteContext(nContext);
    delete nWindow;
}
//-----------------------------------------------------------------------------

} // namespace

//-----------------------------------------------------------------------------
int main(int argc, char** argv) {
    if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) < 0) {
        cout << "[main] SDL_Init failed." << endl;
        return 0;
    }

    try {
        Run();
    }
    catch (const exception& ex) {
        cout << ex.what() << endl;
    }
    catch (...) {
        cout << "Undefined exception." << endl;
    }

    Cleanup();
    SDL_Quit();

    return 0;
}
//-----------------------------------------------------------------------------
